<?php


require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Lz.php';
require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'LzPod.php';


/**
 *  Class for path building.
 *
 *  @author     GoniX aka Giedrius Dubinskas
 *  @created    2007-10-24
 *  @changed    2008-08-03
 *      * Constructor accepts separator instead of path parts
 *      + separator() method to return separator
 *      - LzPath() function
 *      + make() static method as shortcut to create LzPath and add path
 *      - isRoot(), isAbsolute() methods
 *  @changed    2008-08-07
 *      + up(), dir(), base(), ext() methods
 *      - make() static method, use Lz::def() instead
 *  @changed    2008-08-11
 *      + split() method
 *      + clear(), concat() protected methods
 *      * Refactored to internally store string instead of array of parts
 */
class LzPath
        extends LzPod
{
    /**
     *  Constructor.
     *
     *  @param  $separator  [char] Optional separator used to separate
     *                      path parts added with @c add() method.
     *                      If string longer than on character passed
     *                      only first character is used.
     *  @param  $...        [mixed] Optionally any number of path parts
     *                      may by passed to pass them to @c add() method
     *                      after object construction.
     */
    public
    function __construct($separator = '/', $parts = NULL)
    {
        $this->sep  = substr((string)$separator, 0, 1);

        $args   = func_get_args();
        $this->add(array_slice($args, 1));
    }


    public
    function __toString()
    {
        return (string)$this->data;
    }


    /**
     *  Adds specified parts to the end of the path.
     *
     *  Accepts any number of [mixed] arguments.
     *  If argument is array its elements are recursively added to the
     *  path.
     *
     *  @return [this]
     */
    public
    function add($parts)
    {
        $args   = func_get_args();

        foreach ($args as $arg)
        {
            if (is_array($arg))
            {
                foreach ($arg as $dir)
                    $this->add($dir);
            }
            else if ($arg instanceof self)
            {
                $this->add($arg->data);
            }
            else
            {
                $this->concat($arg);
            }
        }
        return $this;
    }


    /**
     *  Adds specified parts to the beginning path.
     *
     *  Accepts any number of arguments.
     *  If argument is array its elements are recursively added to the
     *  path.
     *
     *  @return [this]
     */
    public
    function prepend($parts)
    {
        $args           = func_get_args();
        $args[]         = $this->data;
        return $this->clear()->add($args);
    }


    /**
     *  Removes last part(s) from path.
     *
     *  @param  $level  [int] Optional number of parts to remove from the
     *                  end of path. If the number is lower than 1
     *                  nothing is done. Default is 1.
     *  @return [this]
     */
    public
    function up($level = 1)
    {
        $level  = (int)$level;
        $dir    = (string)$this;
        while ($level-- > 0) $dir = dirname($dir);
        return $this->assign($dir);
    }


    /**
     *  Returns new path without the last path part(s).
     *
     *  @param  $level  [int] Optional number of parts to remove from the
     *                  end of path. If the number is lower than 1
     *                  copy of current path is returned. Default is 1.
     *  @return [clone] Cone of @c $this without the last path part(s).
     */
    public
    function dir($level = 1)
    {
        return $this->copy()->up($level);
    }


    /**
     *  Returns last part of path.
     *
     *  @param  $cut    [string] Optional string to cut off from end of
     *                  last part of path before returning.
     *                  If string is equal to whole base, nothing is done.
     *  @return [clone] Clone of @c $this containing only the last part
     *                  of path.
     */
    public
    function base($cut = '')
    {
        return $this->prototype()->add(basename($this->data, $cut));
    }


    /**
     *  Returns string after last dot of path last part.
     *
     *  @return [string]
     */
    public
    function ext()
    {
        return pathinfo($this->data, PATHINFO_EXTENSION);
    }


    /**
     *  Returns array of path parts.
     *
     *  @return [array] Path parts.
     */
    public
    function split()
    {
        $str    = trim($this->data, $this->sep);
        return ('' === $str) ? array() : split($this->sep, $str);
    }


    /**
     *  Returns number of parts in path.
     *
     *  @return [int] Number of parts in path.
     */
    public
    function count()
    {
        return count($this->split());
    }


    /**
     *  Checks weather file or directory exists.
     *
     *  Accepts any number of arguments.
     *  If argument is array its elements are recursively added to the
     *  path.
     *
     *  @return [bool]
     */
    public
    function exists($tail = NULL)
    {
        $args   = func_get_args();
        return file_exists($this->copy()->add($args));
    }


    /**
     *  Checks weather file or directory exists in include path.
     *
     *  Accepts any number of arguments.
     *  If argument is array its elements are recursively added to the
     *  path.
     *
     *  @return [bool]
     */
    public
    function existsInclude($tail = NULL)
    {
        $args   = func_get_args();
        if ($this->exists($args))
            return true;

        foreach (explode(Lz::PS, get_include_path()) as $dir)
        {
            if ($this->copy()->prepend($dir)->exists($args))
                return true;
        }

        return false;
    }


    /**
     *  Returns separator passed to constructor.
     *
     *  @return [char]
     */
    public
    function separator()
    {
        return $this->sep;
    }


    /**
     *  Adds part to path end.
     *
     *  @param  $part   [string] Part to add to the end of path.
     *  @return [this]
     */
    protected
    function concat($part)
    {
        if ('' === $part || NULL === $part || false === $part)
            return $this;

        if ('' === $this->data)
        {
            if ($part != $this->sep)
                $part   = rtrim($part, $this->sep);

            $this->data .= $part;
        }
        else
        {
            if (substr($this->data, -1) != $this->sep)
                $this->data .= $this->sep;

            $this->data .= trim($part, $this->sep);
        }

        return $this;
    }


    /**
     *  Clears path.
     *
     *  @return [this]
     */
    protected
    function clear()
    {
        return $this->assign('');
    }


    /**
     *  Overriden parent::prototype() method to make default data an empty
     *  string instead of NULL for easier use.
     */
    protected
    function prototype(&$data = '')
    {
        return parent::prototype($data);
    }


    protected   $data   = '';
    protected   $sep    = Lz::DS;
}


function LzPath($separator = '/', $parts = array())
{
    $args   = func_get_args();
    return new LzPath($separator, $args);
}

